/**
 * 用户相关中间件
 *
 * Created by snowingsea on 2020/01/22.
 */
const { I18n } = require('i18n');
const path = require('path');
const auth = require('./../../models/auth');
const users = require('./../../models/users');
const PhoenixError = require('./../../models/phoenix-error');
const mongoNumber = require('../../models/numbers');
const locales = new Map();

/**
 * 获取国际化对象
 * @param {String} locale
 * @returns {Object}
 */
function getLocale(locale) {
  if (!locales.has(locale)) {
    // 国际化
    const i18n = new I18n();
    i18n.configure({
      locales: ['en-US', 'zh-CN'], // 声明包含语言
      directory: path.join(__dirname, '..', '..', 'locales'), // 设置语言文件目录
      objectNotation: true,
    });
    i18n.setLocale(locale);
    locales.set(locale, i18n);
  }
  return locales.get(locale);
}

/**
 * 判断访问令牌未过期
 * @param req
 * @param res
 * @param next
 */
function requireTokenUnexpired(req, res, next) {
  const headerToken = req.get('Authorization');
  if (!headerToken) {
    next(new PhoenixError('invalid token', 'Missing header parameter. "Authorization" is required'));
    return;
  }
  const matches = headerToken.match(/Bearer\s(\S+)/);
  if (!matches) {
    next(new PhoenixError('invalid token', 'Malformed auth header'));
    return;
  }
  const bearerToken = matches[1];
  auth.getToken(bearerToken)
    .then((token) => {
      if (!token) {
        return Promise.reject(new PhoenixError('invalid token', 'The token is invalid or expired'));
      }
      req.$token = token;
      return users.getUser(token.userId);
    })
    .then((user) => {
      req.$me = user;
      req.$owner = user;
      if (user) {
        req.$locale = getLocale(user.lang);
        return Promise.resolve();
      }
      return auth.removeTokens(req.$token.userId);
    })
    .then(() => {
      if (!req.$me) {
        return Promise.reject(new PhoenixError('invalid token', 'The token is invalid or expired'));
      }
      return users.updateUserVisitTime(req.$token.userId);
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 判断当前用户是否可用
 * @param req
 * @param res
 * @param next
 */
function requireAvailable(req, res, next) {
  let err;
  if (!req.$me) {
    err = new PhoenixError('invalid token', 'Missing header parameter. "Authorization" is required');
  } else {
    switch (req.$me.status) {
      case users.statusKind.deleting:
        err = new PhoenixError('forbidden', 'The user is deleting');
        break;
      default:
        break;
    }
  }
  next(err);
}

/**
 * 判断修改后的密码是否与原密码不一致
 * @param req
 * @param res
 * @param next
 */
function requirePasswordDifferent(req, res, next) {
  let err;
  const { oldPassword, newPassword } = req.body;
  if (oldPassword === newPassword) {
    err = new PhoenixError('over range', 'New password should not be same as old password');
  }
  next(err);
}

/**
 * 判断当前用户是否有测试人员权限
 * @param req
 * @param res
 * @param next
 */
function requireTester(req, res, next) {
  let err;
  if (!req.$me) {
    err = new PhoenixError('invalid token', 'Missing header parameter. "Authorization" is required');
  } else if ((req.$me.role & users.roleKind.tester) === 0) { // eslint-disable-line no-bitwise
    err = new PhoenixError('forbidden', 'Permission denied, should be a tester');
  }
  next(err);
}

/**
 * 判断当前用户是否有系统管理员权限
 * @param req
 * @param res
 * @param next
 */
function requireAdministrator(req, res, next) {
  let err;
  const { $me } = req;
  if (!$me) {
    err = new PhoenixError('invalid token', 'Missing header parameter. "Authorization" is required');
  } else if (($me.role & users.roleKind.administrator) === 0) { // eslint-disable-line no-bitwise
    err = new PhoenixError('forbidden', 'Permission denied, should be a administrator');
  }
  next(err);
}

/**
 * 判断当前用户是否有测试经理权限
 * @param req
 * @param res
 * @param next
 */
function requireTestManager(req, res, next) {
  let err;
  const { $me } = req;
  if (!$me) {
    err = new PhoenixError('invalid token', 'Missing header parameter. "Authorization" is required');
  } else if (($me.role & users.roleKind.testManager) === 0) { // eslint-disable-line no-bitwise
    err = new PhoenixError('forbidden', 'Permission denied, should be a test manager');
  }
  next(err);
}

/**
 * 判断URL参数中用户Id是否存在
 * @param req
 * @param res
 * @param next
 */
function requireUserExist(req, res, next) {
  if (!req.params.userId) {
    next(new PhoenixError('invalid request', 'Missing parameters. "userId" is required'));
    return;
  }

  users.getUser(req.params.userId)
    .then((user) => {
      if (!user) {
        return Promise.reject(new PhoenixError('not found', 'Not found, user not found'));
      }
      req.$user = user;
      return Promise.resolve();
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取超级用户
 * @param req
 * @param res
 * @param next
 */
function getSuperUser(req, res, next) {
  users.getUserByName(global.config.sampleManager.username)
    .then((user) => {
      req.$superUser = user;
      req.$owner = user;
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}
function getLockByVersion(params) {
  params = params || {};
  const {
    expireSec = 60,
    retryInterval = 100,
  } = params;
  return async (req, res, next) => {
    if (!global.config.auth.lock) {
      return next();
    }
    let shouldRetry = false;
    let { enabled } = params;
    if (global.config.auth.multiUserLogin) {
      shouldRetry = true;
      enabled = true;
    }
    if (!enabled) {
      return next();
    }
    const lockKey = mongoNumber.getVersionLockKey(req.$projectVersion.masterVersionId || req.$projectVersion._id);
    try {
      await mongoNumber.waitLock(lockKey, { expireSec, retryInterval, shouldRetry });
    } catch (err) {
      next(new PhoenixError(err));
    }
    req.$lockKey = lockKey;
    return next();
  };
}

function checkVersionLock(req, res, next) {
  mongoNumber.checkLockStatus(mongoNumber.getVersionLockKey(req.$projectVersion._id))
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * Export model definition object.
 */
module.exports = {
  checkVersionLock,

  requireTokenUnexpired,
  requireAvailable,
  requireTester,
  requireAdministrator,
  requireTestManager,
  requirePasswordDifferent,
  requireUserExist,

  getSuperUser,
  getLockByVersion,
};
